#ifndef	SLOTS_BINARYSTREAM_H
#define SLOTS_BINARYSTREAM_H

#include <ios>
#include <memory>
#include <string>
#include <vector>
#include <list>
#include <deque>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <valarray>
#include <bitset>
#include <iterator>
#include <limits>
#include <stdexcept>
#include <type_traits>

#include "config.h"
#include "utility.h"
#include "membuf.h"

BEGIN_SLOTS_NS

class binarystream
{
public:
	class sentry
	{
	public:
		sentry(binarystream & s)
			: s_(s), state_(s_.state_ | std::ios::badbit)
		{
			SLOTS_ASSERT(s_.good(), std::domain_error);
			s_.buf_.lock();
		}

		~sentry()
		{
			s_.buf_.unlock();
			s_.state_ = state_;
		}

		void operator=(std::ios::iostate state)
		{
			state_ = state;
		}

	private:
		binarystream & s_;
		std::ios::iostate state_;

	}; // class sentry

	binarystream(membuf & mb)
		: buf_(mb), state_(std::ios::goodbit)
	{
	}

	~binarystream()
	{
	}

	bool good()const
	{
		return std::ios::goodbit == state_;
	}

	bool bad()const
	{
		return !this->good();
	}

	void state(std::ios::iostate s)
	{
		// TODO:
	}

	void clear(bool raise = false)
	{
		// TODO:
	}

	const membuf & buf()const
	{
		return buf_;
	}

	membuf & buf()
	{
		return buf_;
	}

	template<class T>
	binarystream & operator<<(const T & t)
	{
		static_assert(std::is_pod<T>::value, "T must be pod type");
		return this->write_i(&t, sizeof(t));
	}

	template<class T1, class T2>
	binarystream & operator<<(const std::pair<T1, T2> & pair)
	{
		sentry s(*this);
		*this << pair.first << pair.second;
		s = std::ios::goodbit;
		return *this;
	}

	template<class T, class Tx, class Ax>
	binarystream & operator<<(const std::basic_string<T, Tx, Ax> & s)
	{
		return this->write_i(
			s.begin(), s.end(), s.size(),
			std::integral_constant<bool, 
#ifdef SLOTS_STD_STRING_CONTIGUOUS
			true
#else
			false
#endif
			>(),
			std::integral_constant<bool, std::is_pod<T>::value>());
	}

	template<class T, class Ax>
	binarystream & operator<<(const std::vector<T, Ax> & v)
	{
		return this->write_i(
			v.begin(), v.end(), v.size(),
			std::integral_constant<bool, true>(),
			std::integral_constant<bool, std::is_pod<T>::value>());
	}

	template<class Ax>
	binarystream & operator<<(const std::vector<bool, Ax> & v)
	{
		return this->write_i(
			v.begin(), v.end(), v.size(),
			std::integral_constant<bool, false>(),
			std::integral_constant<bool, std::is_pod<bool>::value>());
	}

	template<class T, class Ax>
	binarystream & operator<<(const std::list<T, Ax> & l)
	{
		return this->write_i(
			l.begin(), l.end(), l.size(),
			std::integral_constant<bool, false>(),
			std::integral_constant<bool, std::is_pod<T>::value>());
	}

	template<class T, class Ax>
	binarystream & operator<<(const std::deque<T, Ax> & q)
	{
		return this->write_i(
			q.begin(), q.end(), q.size(),
			std::integral_constant<bool, false>(),
			std::integral_constant<bool, std::is_pod<T>::value>());
	}

	template<class T, class C>
	binarystream & operator<<(const std::stack<T, C> & s)
	{
		typedef _std_adaptor_helper<std::stack<T, C> > adaptor_helper;
		const adaptor_helper * adaptor = 
			reinterpret_cast<const adaptor_helper *>(&s);
		return *this << adaptor->getc();
	}

	template<class T, class C>
	binarystream & operator<<(const std::queue<T, C> & q)
	{
		typedef _std_adaptor_helper<std::queue<T, C> > adaptor_helper;
		const adaptor_helper * adaptor = 
			reinterpret_cast<const adaptor_helper *>(&q);
		return *this << adaptor->getc();
	}

	template<class T, class C, class P>
	binarystream & operator<<(const std::priority_queue<T, C, P> & q)
	{
		typedef _std_adaptor_helper<
			std::priority_queue<T, C, P> > adaptor_helper;
		const adaptor_helper * adaptor = 
			reinterpret_cast<const adaptor_helper *>(&q);
		return *this << adaptor->getc();
	}

	template<class K, class V, class P, class Ax>
	binarystream & operator<<(const std::map<K, V, P, Ax> & m)
	{
		return this->write_i(
			m.begin(), m.end(), m.size(),
			std::integral_constant<bool, false>(),
			std::integral_constant<bool, false>());
	}

	template<class K, class P, class Ax>
	binarystream & operator<<(const std::set<K, P, Ax> & s)
	{
		return this->write_i(
			s.begin(), s.end(), s.size(),
			std::integral_constant<bool, false>(),
			std::integral_constant<bool, false>());
	}

	template<class T>
	binarystream & operator<<(const std::valarray<T> & v)
	{
		static_assert(std::is_pod<T>::value, "T must be pod type");
		return this->write_i(v);
	}

	template<size_t N>
	binarystream & operator<<(std::bitset<N> & v)
	{
		return this->write_i(v);
	}

	template<class T>
	binarystream & operator>>(T & t)
	{
		static_assert(std::is_pod<T>::value, "T must be pod type");
		return this->read_i(&t, sizeof(t));
	}

	template<class T1, class T2>
	binarystream & operator>>(std::pair<T1, T2> & pair)
	{
		sentry s(*this);
		*this >> pair.first >> pair.second;
		s = std::ios::goodbit;
		return *this;
	}

	template<class T, class Tx, class Ax>
	binarystream & operator>>(std::basic_string<T, Tx, Ax> & s)
	{
		return this->read_i(s,
			std::integral_constant<bool,
#ifdef SLOTS_STD_STRING_CONTIGUOUS
			true
#else
			false
#endif
			>(), std::integral_constant<bool, std::is_pod<T>::value>());
	}

	template<class T, class Ax>
	binarystream & operator>>(std::vector<T, Ax> & v)
	{
		return this->read_i(v,
			std::integral_constant<bool, true>(),
			std::integral_constant<bool, std::is_pod<T>::value>());
	}

	template<class T, class Ax>
	binarystream & operator>>(std::list<T, Ax> & l)
	{
		return this->read_i(v,
			std::integral_constant<bool, false>(),
			std::integral_constant<bool, std::is_pod<T>::value>());
	}

	template<class T, class Ax>
	binarystream & operator>>(std::deque<T, Ax> & q)
	{
		return this->read_i(v,
			std::integral_constant<bool, false>(),
			std::integral_constant<bool, std::is_pod<T>::value>());
	}

	template<class T, class C>
	binarystream & operator>>(std::stack<T, C> & st)
	{
		typedef _std_adaptor_helper<std::stack<T, C> > adaptor_helper;
		const adaptor_helper * adaptor = 
			reinterpret_cast<const adaptor_helper *>(&st);
		return *this >> adaptor->getc();
	}

	template<class T, class C>
	binarystream & operator>>(std::queue<T, C> & qu)
	{
		typedef _std_adaptor_helper<std::queue<T, C> > adaptor_helper;
		const adaptor_helper * adaptor = 
			reinterpret_cast<const adaptor_helper *>(&qu);
		return *this >> adaptor->getc();
	}

	template<class T, class C, class P>
	binarystream & operator>>(std::priority_queue<T, C, P> & qu)
	{
		typedef _std_adaptor_helper<
			std::priority_queue<T, C, P> > adaptor_helper;
		const adaptor_helper * adaptor = 
			reinterpret_cast<const adaptor_helper *>(&st);
		return *this >> adaptor->getc();
	}

	template<class K, class V, class P, class Ax>
	binarystream & operator>>(std::map<K, V, P, Ax> & m)
	{
		return this->read_i(m);
	}

	template<class K, class P, class Ax>
	binarystream & operator>>(std::set<K, P, Ax> & set)
	{
		return this->read_i(set);
	}

	template<class T>
	binarystream & operator>>(std::valarray<T> & v)
	{
		static_assert(std::is_pod<T>::value, "T must be pod type");
		return this->read_i(v);
	}

	template<size_t N>
	binarystream & operator>>(std::bitset<N> & v)
	{
		return this->read_i(v);
	}

private:
	binarystream(const binarystream &);
	const binarystream & operator=(const binarystream &);

	template<class T>
	binarystream & operator<<(const T *);
	template<class T>
	binarystream & operator>>(const T * &);
	binarystream & operator<<(const void *);
	binarystream & operator>>(const void * &);

	friend class sentry;

	binarystream & write_i(const void * p, size_t n)
	{
		sentry s(*this);
		buf_.write(p, n);
		s = std::ios::goodbit;
		return *this;
	}

	template<class Iter, bool Continuous, bool IsPod>
	binarystream & write_i(
		Iter b, Iter e, size_t n,
		const std::integral_constant<bool, Continuous>,
		const std::integral_constant<bool, IsPod>)
	{
		sentry s(*this);
		*this << n;
		for(; b != e; ++b)
			*this << *b;
		s = std::ios::goodbit;
		return *this;
	}

	template<class Iter>
	binarystream & write_i(
		Iter b, Iter e, size_t n,
		const std::integral_constant<bool, true>,
		const std::integral_constant<bool, true>)
	{
		sentry s(*this);
		*this << n;
		if (b != e) 
			buf_.write(&*b, n * sizeof(*b));
		s = std::ios::goodbit;
		return *this;
	}

	template<class T>
	binarystream & write_i(const std::valarray<T> & v)
	{
		sentry s(*this);
		*this << v.size();
		for(size_t i = 0; i < v.size(); ++i)
			*this << v[i];
		s = std::ios::goodbit;
		return *this;
	}

	template<size_t N>
	binarystream & write_i(const std::bitset<N> & v)
	{
		sentry s(*this);
		*this << v.size();
		for(size_t i = 0; i < v.size(); ++i)
			*this << v[i];
		s = std::ios::goodbit;
		return *this;
	}

	binarystream & read_i(void * p, size_t n)
	{
		sentry s(*this);
		buf_.read(p, n);
		s = std::ios::goodbit;
		return *this;
	}

	// std sequence
	template<class Sequence, bool Continuous, bool IsPod>
	binarystream & read_i(
		Sequence & seq, std::integral_constant<bool, Continuous>,
		std::integral_constant<bool, IsPod>)
	{
		typedef typename Sequence::value_type value_type;
		sentry s(*this);

		size_t n;
		*this >> n;
		const size_t tmp = seq.size();
		try {
			for(size_t i = 0; i < n; ++i)
			{
				value_type val;
				*this >> val;
				seq.push_back(val);
			}
		}catch(...)
		{
			seq.resize(tmp);
			throw;
		}

		s = std::ios::goodbit;
		return *this;
	}

	// std sequence, continuous memory store
	template<class Sequence>
	binarystream & read_std_sequence(
		Sequence & seq, std::integral_constant<bool, true>,
		std::integral_constant<bool, true>)
	{
		typedef typename Sequence::value_type value_type;
		sentry s(*this);

		size_t n;
		*this >> n;
		SLOTS_ASSERT(
			std::numeric_limits<size_t>::max() / n >= sizeof(value_type),
			std::length_error);
		SLOTS_ASSERT(
			buf_.size() >= n * sizeof(value_type),
			std::length_error);

		const size_t tmp = seq.size();
		seq.resize(tmp + n);
		try {
			buf_.read(&seq[tmp], n * sizeof(T));
		}catch(...)
		{
			seq.resize(tmp);
			throw;
		}

		s = std::ios::goodbit;
		return *this;
	}

	template<class K, class V, class P, class A>
	binarystream & read_i(std::map<K, V, P, A> & m)
	{
		typedef typename std::pair<K, V> value_type;
		sentry s(*this);

		size_t n;
		*this >> n;
		std::vector<K, A> keys;
		try {
			for(size_t i = 0; i < n; ++i)
			{
				value_type val;
				*this >> val;
				m.insert(val);
				keys.push_back(val.first);
			}
		}catch(...)
		{
			std::vector<K, A>::iterator it;
			for(it = keys.begin(); it != keys.end(); ++it)
				m.erase(*it);
			throw;
		}

		s = std::ios::goodbit;
		return *this;
	}

	template<class K, class P, class A>
	binarystream & read_i(std::set<K, P, A> & set)
	{
		typedef typename std::map<K, V, P, A>::value_type value_type;
		sentry s(*this);

		size_t n;
		*this >> n;
		std::vector<K, A> keys;
		try {
			for(size_t i = 0; i < n; ++i)
			{
				value_type val;
				*this >> val;
				m.insert(val);
				keys.push_back(val);
			}
		}catch(...)
		{
			std::vector<K, A>::iterator it;
			for(it = keys.begin(); it != keys.end(); ++it)
				m.erase(*it);
			throw;
		}

		s = std::ios::goodbit;
		return *this;
	}

	template<class T>
	binarystream & read_i(std::valarray<T> & v)
	{
		sentry s(*this);
		size_t n;
		*this >> n;
		SLOTS_ASSERT(
			std::numeric_limits<size_t>::max() / n >= sizeof(T),
			std::length_error);
		SLOTS_ASSERT(
			buf_.size() >= n * sizeof(T),
			std::length_error);

		const size_t tmp = v.size();
		v.resize(tmp + n);
		try{
			for(size_t i = 0; i < n; ++i)
				*this >> v[tmp + i];
		}catch(...)
		{
			v.resize(tmp);
			throw;
		}
			
			
		s = std::ios::goodbit;
		return *this;
	}

	template<size_t N>
	binarystream & read_i(std::bitset<N> & v)
	{
		sentry s(*this);
		size_t n;
		*this >> n;
		SLOTS_ASSERT(N == n, std::length_error);

		std::bitset<N> tmp = v;
		try{
			for(size_t i = 0; i < n; ++i)
			{
				bool b;
				*this >> b;
				v[i] = b;
			}
		}catch(...)
		{
			v = tmp;
			throw;
		}
			
			
		s = std::ios::goodbit;
		return *this;
	}

	membuf & buf_;
	int state_;

}; // class binarystream

END_SLOTS_NS

#endif

